home *** CD-ROM | disk | FTP | other *** search
/ BCI NET 2 / BCI NET 2.iso / archives / utilities / text / less-278.lha / less-278 / src.lha / source / ch.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-01  |  15.1 KB  |  764 lines

  1. /*
  2.  * Copyright (c) 1984,1985,1989,1994,1995  Mark Nudelman
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice in the documentation and/or other materials provided with 
  12.  *    the distribution.
  13.  *
  14.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
  15.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  17.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
  18.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  19.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
  20.  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
  21.  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  22.  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
  23.  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
  24.  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25.  */
  26.  
  27.  
  28. /*
  29.  * Low level character input from the input file.
  30.  * We use these special purpose routines which optimize moving
  31.  * both forward and backward from the current read pointer.
  32.  */
  33.  
  34. #include "less.h"
  35.  
  36. public int ignore_eoi;
  37.  
  38. /*
  39.  * Pool of buffers holding the most recently used blocks of the input file.
  40.  * The buffer pool is kept as a doubly-linked circular list,
  41.  * in order from most- to least-recently used.
  42.  * The circular list is anchored by the file state "thisfile".
  43.  */
  44. #define LBUFSIZE    1024
  45. struct buf {
  46.     struct buf *next, *prev;  /* Must be first to match struct filestate */
  47.     long block;
  48.     unsigned int datasize;
  49.     unsigned char data[LBUFSIZE];
  50. };
  51.  
  52. /*
  53.  * The file state is maintained in a filestate structure.
  54.  * A pointer to the filestate is kept in the ifile structure.
  55.  */
  56. struct filestate {
  57.     /* -- Following members must match struct buf */
  58.     struct buf *buf_next, *buf_prev;
  59.     long buf_block;
  60.     /* -- End of struct buf copy */
  61.     int file;
  62.     int flags;
  63.     POSITION fpos;
  64.     int nbufs;
  65.     long block;
  66.     int offset;
  67.     POSITION fsize;
  68. };
  69.  
  70.  
  71. #define    END_OF_CHAIN    ((struct buf *)thisfile)
  72. #define    ch_bufhead    thisfile->buf_next
  73. #define    ch_buftail    thisfile->buf_prev
  74. #define    ch_nbufs    thisfile->nbufs
  75. #define    ch_block    thisfile->block
  76. #define    ch_offset    thisfile->offset
  77. #define    ch_fpos        thisfile->fpos
  78. #define    ch_fsize    thisfile->fsize
  79. #define    ch_flags    thisfile->flags
  80. #define    ch_file        thisfile->file
  81.  
  82. static struct filestate *thisfile;
  83. static int ch_ungotchar = -1;
  84.  
  85. extern int autobuf;
  86. extern int sigs;
  87. extern int cbufs;
  88. extern IFILE curr_ifile;
  89. #if LOGFILE
  90. extern int logfile;
  91. extern char *namelogfile;
  92. #endif
  93.  
  94. static int ch_addbuf();
  95.  
  96.  
  97. /*
  98.  * Get the character pointed to by the read pointer.
  99.  * ch_get() is a macro which is more efficient to call
  100.  * than fch_get (the function), in the usual case 
  101.  * that the block desired is at the head of the chain.
  102.  */
  103. #define    ch_get()   ((ch_block == ch_bufhead->block && \
  104.              ch_offset < ch_bufhead->datasize) ? \
  105.             ch_bufhead->data[ch_offset] : fch_get())
  106.     int
  107. fch_get()
  108. {
  109.     register struct buf *bp;
  110.     register int n;
  111.     register int slept;
  112.     POSITION pos;
  113.     POSITION len;
  114.  
  115.     slept = FALSE;
  116.  
  117.     /*
  118.      * Look for a buffer holding the desired block.
  119.      */
  120.     for (bp = ch_bufhead;  bp != END_OF_CHAIN;  bp = bp->next)
  121.         if (bp->block == ch_block)
  122.         {
  123.             if (ch_offset >= bp->datasize)
  124.                 /*
  125.                  * Need more data in this buffer.
  126.                  */
  127.                 goto read_more;
  128.             goto found;
  129.         }
  130.     /*
  131.      * Block is not in a buffer.  
  132.      * Take the least recently used buffer 
  133.      * and read the desired block into it.
  134.      * If the LRU buffer has data in it, 
  135.      * then maybe allocate a new buffer.
  136.      */
  137.     if (ch_buftail == END_OF_CHAIN || ch_buftail->block != (long)(-1))
  138.     {
  139.         /*
  140.          * There is no empty buffer to use.
  141.          * Allocate a new buffer if:
  142.          * 1. We can't seek on this file and -b is not in effect; or
  143.          * 2. We haven't allocated the max buffers for this file yet.
  144.          */
  145.         if ((autobuf && !(ch_flags & CH_CANSEEK)) ||
  146.             (cbufs == -1 || ch_nbufs < cbufs))
  147.             if (ch_addbuf())
  148.                 /*
  149.                  * Allocation failed: turn off autobuf.
  150.                  */
  151.                 autobuf = OPT_OFF;
  152.     }
  153.     bp = ch_buftail;
  154.     bp->block = ch_block;
  155.     bp->datasize = 0;
  156.  
  157.     read_more:
  158.     pos = (ch_block * LBUFSIZE) + bp->datasize;
  159.     if ((len = ch_length()) != NULL_POSITION && pos >= len)
  160.         /*
  161.          * At end of file.
  162.          */
  163.         return (EOI);
  164.  
  165.     if (pos != ch_fpos)
  166.     {
  167.         /*
  168.          * Not at the correct position: must seek.
  169.          * If input is a pipe, we're in trouble (can't seek on a pipe).
  170.          * Some data has been lost: just return "?".
  171.          */
  172.         if (!(ch_flags & CH_CANSEEK))
  173.             return ('?');
  174.         if (lseek(ch_file, (off_t)pos, 0) == BAD_LSEEK)
  175.         {
  176.              error("seek error", NULL_PARG);
  177.             clear_eol();
  178.             return (EOI);
  179.          }
  180.          ch_fpos = pos;
  181.      }
  182.  
  183.     /*
  184.      * Read the block.
  185.      * If we read less than a full block, that's ok.
  186.      * We use partial block and pick up the rest next time.
  187.      */
  188.     if (ch_ungotchar == -1)
  189.     {
  190.         n = iread(ch_file, &bp->data[bp->datasize], 
  191.             (unsigned int)(LBUFSIZE - bp->datasize));
  192.     } else
  193.     {
  194.         bp->data[bp->datasize] = ch_ungotchar;
  195.         n = 1;
  196.         ch_ungotchar = -1;
  197.     }
  198.  
  199.     if (n == READ_INTR)
  200.         return (EOI);
  201.     if (n < 0)
  202.     {
  203.         error("read error", NULL_PARG);
  204.         clear_eol();
  205.         n = 0;
  206.     }
  207.  
  208. #if LOGFILE
  209.     /*
  210.      * If we have a log file, write the new data to it.
  211.      */
  212.     if (logfile >= 0 && n > 0)
  213.         write(logfile, (char *) &bp->data[bp->datasize], n);
  214. #endif
  215.  
  216.     ch_fpos += n;
  217.     bp->datasize += n;
  218.  
  219.     /*
  220.      * If we have read to end of file, set ch_fsize to indicate
  221.      * the position of the end of file.
  222.      */
  223.     if (n == 0)
  224.     {
  225.         ch_fsize = pos;
  226.         if (ignore_eoi)
  227.         {
  228.             /*
  229.              * We are ignoring EOF.
  230.              * Wait a while, then try again.
  231.              */
  232.             if (!slept)
  233.                 ierror("Waiting for data", NULL_PARG);
  234. #if !MSOFTC
  235.              sleep(1);
  236. #endif
  237.             slept = TRUE;
  238.         }
  239.         if (ABORT_SIGS())
  240.             return (EOI);
  241.     }
  242.  
  243.     found:
  244.     if (ch_bufhead != bp)
  245.     {
  246.         /*
  247.          * Move the buffer to the head of the buffer chain.
  248.          * This orders the buffer chain, most- to least-recently used.
  249.          */
  250.         bp->next->prev = bp->prev;
  251.         bp->prev->next = bp->next;
  252.  
  253.         bp->next = ch_bufhead;
  254.         bp->prev = END_OF_CHAIN;
  255.         ch_bufhead->prev = bp;
  256.         ch_bufhead = bp;
  257.     }
  258.  
  259.     if (ch_offset >= bp->datasize)
  260.         /*
  261.          * After all that, we still don't have enough data.
  262.          * Go back and try again.
  263.          */
  264.         goto read_more;
  265.  
  266.     return (bp->data[ch_offset]);
  267. }
  268.  
  269. /*
  270.  * ch_ungetchar is a rather kludgy and limited way to push 
  271.  * a single char onto an input file descriptor.
  272.  */
  273.     public void
  274. ch_ungetchar(c)
  275.     int c;
  276. {
  277.     if (c != -1 && ch_ungotchar != -1)
  278.         error("ch_ungetchar overrun", NULL_PARG);
  279.     ch_ungotchar = c;
  280. }
  281.  
  282. #if LOGFILE
  283. /*
  284.  * Close the logfile.
  285.  * If we haven't read all of standard input into it, do that now.
  286.  */
  287.     public void
  288. end_logfile()
  289. {
  290.     static int tried = FALSE;
  291.  
  292.     if (logfile < 0)
  293.         return;
  294.     if (!tried && ch_fsize == NULL_POSITION)
  295.     {
  296.         tried = TRUE;
  297.         ierror("Finishing logfile", NULL_PARG);
  298.         while (ch_forw_get() != EOI)
  299.             if (ABORT_SIGS())
  300.                 break;
  301.     }
  302.     close(logfile);
  303.     logfile = -1;
  304.     namelogfile = NULL;
  305. }
  306.  
  307. /*
  308.  * Start a log file AFTER less has already been running.
  309.  * Invoked from the - command; see toggle_option().
  310.  * Write all the existing buffered data to the log file.
  311.  */
  312.     public void
  313. sync_logfile()
  314. {
  315.     register struct buf *bp;
  316.     int warned = FALSE;
  317.     long block;
  318.     long nblocks;
  319.  
  320.     nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE;
  321.     for (block = 0;  block < nblocks;  block++)
  322.     {
  323.         for (bp = ch_bufhead;  ;  bp = bp->next)
  324.         {
  325.             if (bp == END_OF_CHAIN)
  326.             {
  327.                 if (!warned)
  328.                 {
  329.                     error("Warning: log file is incomplete",
  330.                         NULL_PARG);
  331.                     warned = TRUE;
  332.                 }
  333.                 break;
  334.             }
  335.             if (bp->block == block)
  336.             {
  337.                 write(logfile, (char *) bp->data, bp->datasize);
  338.                 break;
  339.             }
  340.         }
  341.     }
  342. }
  343.  
  344. #endif
  345.  
  346. /*
  347.  * Determine if a specific block is currently in one of the buffers.
  348.  */
  349.     static int
  350. buffered(block)
  351.     long block;
  352. {
  353.     register struct buf *bp;
  354.  
  355.     for (bp = ch_bufhead;  bp != END_OF_CHAIN;  bp = bp->next)
  356.         if (bp->block == block)
  357.             return (TRUE);
  358.     return (FALSE);
  359. }
  360.  
  361. /*
  362.  * Seek to a specified position in the file.
  363.  * Return 0 if successful, non-zero if can't seek there.
  364.  */
  365.     public int
  366. ch_seek(pos)
  367.     register POSITION pos;
  368. {
  369.     long new_block;
  370.     POSITION len;
  371.  
  372.     len = ch_length();
  373.     if (pos < ch_zero() || (len != NULL_POSITION && pos > len))
  374.         return (1);
  375.  
  376.     new_block = pos / LBUFSIZE;
  377.     if (!(ch_flags & CH_CANSEEK) && pos != ch_fpos && !buffered(new_block))
  378.     {
  379.         if (ch_fpos > pos)
  380.             return (1);
  381.         while (ch_fpos < pos)
  382.         {
  383.             if (ch_forw_get() == EOI)
  384.                 return (1);
  385.             if (ABORT_SIGS())
  386.                 return (1);
  387.         }
  388.         return (0);
  389.     }
  390.     /*
  391.      * Set read pointer.
  392.      */
  393.     ch_block = new_block;
  394.     ch_offset = pos % LBUFSIZE;
  395.     return (0);
  396. }
  397.  
  398. /*
  399.  * Seek to the end of the file.
  400.  */
  401.     public int
  402. ch_end_seek()
  403. {
  404.     POSITION len;
  405.  
  406.     if (ch_flags & CH_CANSEEK)
  407.         ch_fsize = filesize(ch_file);
  408.  
  409.     len = ch_length();
  410.     if (len != NULL_POSITION)
  411.         return (ch_seek(len));
  412.  
  413.     /*
  414.      * Do it the slow way: read till end of data.
  415.      */
  416.     while (ch_forw_get() != EOI)
  417.         if (ABORT_SIGS())
  418.             return (1);
  419.     return (0);
  420. }
  421.  
  422. /*
  423.  * Seek to the beginning of the file, or as close to it as we can get.
  424.  * We may not be able to seek there if input is a pipe and the
  425.  * beginning of the pipe is no longer buffered.
  426.  */
  427.     public int
  428. ch_beg_seek()
  429. {
  430.     register struct buf *bp, *firstbp;
  431.  
  432.     /*
  433.      * Try a plain ch_seek first.
  434.      */
  435.     if (ch_seek(ch_zero()) == 0)
  436.         return (0);
  437.  
  438.     /*
  439.      * Can't get to position 0.
  440.      * Look thru the buffers for the one closest to position 0.
  441.      */
  442.     firstbp = bp = ch_bufhead;
  443.     if (bp == END_OF_CHAIN)
  444.         return (1);
  445.     while ((bp = bp->next) != END_OF_CHAIN)
  446.         if (bp->block < firstbp->block)
  447.             firstbp = bp;
  448.     ch_block = firstbp->block;
  449.     ch_offset = 0;
  450.     return (0);
  451. }
  452.  
  453. /*
  454.  * Return the length of the file, if known.
  455.  */
  456.     public POSITION
  457. ch_length()
  458. {
  459.     if (ignore_eoi)
  460.         return (NULL_POSITION);
  461.     return (ch_fsize);
  462. }
  463.  
  464. /*
  465.  * Return the current position in the file.
  466.  */
  467. #define    tellpos(blk,off)   ((POSITION)((((long)(blk)) * LBUFSIZE) + (off)))
  468.  
  469.     public POSITION
  470. ch_tell()
  471. {
  472.     return (tellpos(ch_block, ch_offset));
  473. }
  474.  
  475. /*
  476.  * Get the current char and post-increment the read pointer.
  477.  */
  478.     public int
  479. ch_forw_get()
  480. {
  481.     register int c;
  482.  
  483.     c = ch_get();
  484.     if (c == EOI)
  485.         return (EOI);
  486.     if (ch_offset < LBUFSIZE-1)
  487.         ch_offset++;
  488.     else
  489.     {
  490.         ch_block ++;
  491.         ch_offset = 0;
  492.     }
  493.     return (c);
  494. }
  495.  
  496. /*
  497.  * Pre-decrement the read pointer and get the new current char.
  498.  */
  499.     public int
  500. ch_back_get()
  501. {
  502.     if (ch_offset > 0)
  503.         ch_offset --;
  504.     else
  505.     {
  506.         if (ch_block <= 0)
  507.             return (EOI);
  508.         if (!(ch_flags & CH_CANSEEK) && !buffered(ch_block-1))
  509.             return (EOI);
  510.         ch_block--;
  511.         ch_offset = LBUFSIZE-1;
  512.     }
  513.     return (ch_get());
  514. }
  515.  
  516. /*
  517.  * Allocate buffers.
  518.  * Caller wants us to have a total of at least want_nbufs buffers.
  519.  */
  520.     public int
  521. ch_nbuf(want_nbufs)
  522.     int want_nbufs;
  523. {
  524.     PARG parg;
  525.  
  526.     while (ch_nbufs < want_nbufs)
  527.     {
  528.         if (ch_addbuf())
  529.         {
  530.             /*
  531.              * Cannot allocate enough buffers.
  532.              * If we don't have ANY, then quit.
  533.              * Otherwise, just report the error and return.
  534.              */
  535.             parg.p_int = want_nbufs - ch_nbufs;
  536.             error("Cannot allocate %d buffers", &parg);
  537.             if (ch_nbufs == 0)
  538.                 quit(QUIT_ERROR);
  539.             break;
  540.         }
  541.     }
  542.     return (ch_nbufs);
  543. }
  544.  
  545. /*
  546.  * Flush (discard) any saved file state, including buffer contents.
  547.  */
  548.     public void
  549. ch_flush()
  550. {
  551.     register struct buf *bp;
  552.  
  553.     if (!(ch_flags & CH_CANSEEK))
  554.     {
  555.         /*
  556.          * If input is a pipe, we don't flush buffer contents,
  557.          * since the contents can't be recovered.
  558.          */
  559.         ch_fsize = NULL_POSITION;
  560.         return;
  561.     }
  562.  
  563.     /*
  564.      * Initialize all the buffers.
  565.      */
  566.     for (bp = ch_bufhead;  bp != END_OF_CHAIN;  bp = bp->next)
  567.         bp->block = (long)(-1);
  568.  
  569.     /*
  570.      * Figure out the size of the file, if we can.
  571.      */
  572.     ch_fsize = filesize(ch_file);
  573.  
  574.     /*
  575.      * Seek to a known position: the beginning of the file.
  576.      */
  577.     ch_fpos = 0;
  578.     ch_block = 0; /* ch_fpos / LBUFSIZE; */
  579.     ch_offset = 0; /* ch_fpos % LBUFSIZE; */
  580.  
  581.     if (lseek(ch_file, (off_t)0, 0) == BAD_LSEEK)
  582.     {
  583.         /*
  584.          * Warning only; even if the seek fails for some reason,
  585.          * there's a good chance we're at the beginning anyway.
  586.          * {{ I think this is bogus reasoning. }}
  587.          */
  588.         error("seek error to 0", NULL_PARG);
  589.     }
  590. }
  591.  
  592. /*
  593.  * Allocate a new buffer.
  594.  * The buffer is added to the tail of the buffer chain.
  595.  */
  596.     static int
  597. ch_addbuf()
  598. {
  599.     register struct buf *bp;
  600.  
  601.     /*
  602.      * Allocate and initialize a new buffer and link it 
  603.      * onto the tail of the buffer list.
  604.      */
  605.     bp = (struct buf *) calloc(1, sizeof(struct buf));
  606.     if (bp == NULL)
  607.         return (1);
  608.     ch_nbufs++;
  609.     bp->block = (long)(-1);
  610.     bp->next = END_OF_CHAIN;
  611.     bp->prev = ch_buftail;
  612.     ch_buftail->next = bp;
  613.     ch_buftail = bp;
  614.     return (0);
  615. }
  616.  
  617. /*
  618.  * Delete all buffers for this file.
  619.  */
  620.     static void
  621. ch_delbufs()
  622. {
  623.     register struct buf *bp;
  624.  
  625.     while (ch_bufhead != END_OF_CHAIN)
  626.     {
  627.         bp = ch_bufhead;
  628.         bp->next->prev = bp->prev;;
  629.         bp->prev->next = bp->next;
  630.         free(bp);
  631.     }
  632.     ch_nbufs = 0;
  633. }
  634.  
  635. /*
  636.  * Is it possible to seek on a file descriptor?
  637.  */
  638.     public int
  639. seekable(f)
  640.     int f;
  641. {
  642.     return (lseek(f, (off_t)1, 0) != BAD_LSEEK);
  643. }
  644.  
  645. /*
  646.  * Initialize file state for a new file.
  647.  */
  648.     public void
  649. ch_init(f, flags)
  650.     int f;
  651.     int flags;
  652. {
  653.     /*
  654.      * See if we already have a filestate for this file.
  655.      */
  656.     thisfile = (struct filestate *) get_filestate(curr_ifile);
  657.     if (thisfile == NULL)
  658.     {
  659.         /*
  660.          * Allocate and initialize a new filestate.
  661.          */
  662.         thisfile = (struct filestate *) 
  663.                 calloc(1, sizeof(struct filestate));
  664.         thisfile->buf_next = thisfile->buf_prev = END_OF_CHAIN;
  665.         thisfile->buf_block = (long)(-1);
  666.         thisfile->nbufs = 0;
  667.         thisfile->flags = 0;
  668.         thisfile->fpos = 0;
  669.         thisfile->block = 0;
  670.         thisfile->offset = 0;
  671.         thisfile->file = -1;
  672.         thisfile->fsize = NULL_POSITION;
  673.         ch_flags = flags;
  674.         /*
  675.          * Try to seek; set CH_CANSEEK if it works.
  676.          */
  677.         if (seekable(f))
  678.             ch_flags |= CH_CANSEEK;
  679.         set_filestate(curr_ifile, (void *) thisfile);
  680.     }
  681.     if (thisfile->file == -1)
  682.         thisfile->file = f;
  683.     ch_flush();
  684. }
  685.  
  686. /*
  687.  * Close a filestate.
  688.  */
  689.     public void
  690. ch_close()
  691. {
  692.     int keepstate = FALSE;
  693.  
  694.     if (ch_flags & (CH_CANSEEK|CH_POPENED))
  695.     {
  696.         /*
  697.          * We can seek or re-open, so we don't need to keep buffers.
  698.          */
  699.         ch_delbufs();
  700.     } else
  701.         keepstate = TRUE;
  702.     if (!(ch_flags & CH_KEEPOPEN))
  703.     {
  704.         /*
  705.          * We don't need to keep the file descriptor open
  706.          * (because we can re-open it.)
  707.          * But don't really close it if it was opened via popen(),
  708.          * because pclose() wants to close it.
  709.          */
  710.         if (!(ch_flags & CH_POPENED))
  711.             close(ch_file);
  712.         ch_file = -1;
  713.     } else
  714.         keepstate = TRUE;
  715.     if (!keepstate)
  716.     {
  717.         /*
  718.          * We don't even need to keep the filestate structure.
  719.          */
  720.         free(thisfile);
  721.         thisfile = NULL;
  722.         set_filestate(curr_ifile, (void *) NULL);
  723.     }
  724. }
  725.  
  726. /*
  727.  * Return ch_flags for the current file.
  728.  */
  729.     public int
  730. ch_getflags()
  731. {
  732.     return (ch_flags);
  733. }
  734.  
  735. #if 0
  736.     public void
  737. ch_dump(struct filestate *fs)
  738. {
  739.     struct buf *bp;
  740.     unsigned char *s;
  741.  
  742.     if (fs == NULL)
  743.     {
  744.         printf(" --no filestate\n");
  745.         return;
  746.     }
  747.     printf(" file %d, flags %x, fpos %x, fsize %x, blk/off %x/%x\n",
  748.         fs->file, fs->flags, fs->fpos, 
  749.         fs->fsize, fs->block, fs->offset);
  750.     printf(" %d bufs:\n", fs->nbufs);
  751.     for (bp = fs->buf_next; bp != (struct buf *)fs;  bp = bp->next)
  752.     {
  753.         printf("%x: blk %x, size %x \"",
  754.             bp, bp->block, bp->datasize);
  755.         for (s = bp->data;  s < bp->data + 30;  s++)
  756.             if (*s >= ' ' && *s < 0x7F)
  757.                 printf("%c", *s);
  758.             else
  759.                 printf(".");
  760.         printf("\"\n");
  761.     }
  762. }
  763. #endif
  764.